package lxs.swift.collector;


import java.util.*;

public class DictTree {
    private final Map<Character, Node> nodes = new HashMap<>();

    public DictTree() {
    }

    public void insert(String word) {
        Objects.requireNonNull(word);
        if (word.isEmpty()) return;
        char[] chars = word.toCharArray();
        Node head = nodes.computeIfAbsent(chars[0], Node::new);
        for (int i = 1; i < chars.length; i++) {
            char aChar = chars[i];
            head = head.putChild(aChar);
        }
    }

    public boolean containsTheWord(String word) {
        return wordNode(word) != null;
    }

    private Node wordNode(String word) {
        Objects.requireNonNull(word);
        if (word.isEmpty()) throw new RuntimeException("empty string");
        char[] chars = word.toCharArray();
        if (!nodes.containsKey(chars[0])) return null;
        Node node = nodes.get(chars[0]);
        for (int i = 1; i < chars.length; i++) {
            char aChar = chars[i];
            Node child = node.getChild(aChar);
            if (child == null) return null;
            node = child;
        }
        return node;
    }

    public boolean existTheWord(String word) {
        Node node = wordNode(word);
        return node != null && node.isEnd();
    }

    public List<Word> matchWords(String content) {
        Objects.requireNonNull(content);
        if (content.isEmpty()) throw new RuntimeException("empty string");
        List<Word> existWords = new ArrayList<>();
        List<StringBuilder> builders = new ArrayList<>();
        char[] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];
            {
                for (StringBuilder builder : builders) {
                    builder.append(c);
                }
                String str = c + "";
                Node node = this.wordNode(str);
                if (node != null) {
                    builders.add(new StringBuilder(str));
                    if (node.isEnd()) {
                        existWords.add(new Word(str, i - str.length() + 1, i));
                    }
                }
            }
            int finalI = i;
            builders.removeIf(builder -> {
                if (builder.length() == 1) return false;
                String str = builder.toString();
                Node node = this.wordNode(str);
                if (node == null) return true;
                if (node.isEnd()) {
                    existWords.add(new Word(str, finalI - str.length() + 1, finalI));
                }
                return false;
            });
        }
        return existWords;
    }


    public static class Word {
        private final String value;
        private final int startIndex;
        private final int endIndex;

        public Word(String value, int startIndex, int endIndex) {
            this.value = value;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        public String getValue() {
            return value;
        }

        public int getStartIndex() {
            return startIndex;
        }

        public int getEndIndex() {
            return endIndex;
        }

        @Override
        public String toString() {
            return "Word{" +
                    "value='" + value + '\'' +
                    ", startIndex=" + startIndex +
                    ", endIndex=" + endIndex +
                    '}';
        }
    }

    private static class Node {
        protected final char value;
        private final Map<Character, Node> childNodes = new HashMap<>();

        public Node(char value) {
            this.value = value;
        }

        public Node putChild(char value) {
            return childNodes.computeIfAbsent(value, Node::new);
        }

        public Node getChild(char value) {
            return childNodes.get(value);
        }

        public boolean containsNode(char value) {
            return childNodes.containsKey(value);
        }

        public boolean isEnd() {
            return childNodes.isEmpty();
        }
    }
}
